﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using SlimDX;
using SlimDX.Direct3D9;

namespace Editor.Rendering
{
    public class Shader : IComparable
    {
        public delegate void PassRenderDelegate(Device device, VertexBuffer vertexBuffer, Type vertexType, int vertexCount, IndexBuffer indexBuffer, PrimitiveType primitiveType, int primitiveCount);

        private String m_ShaderFileName;
        private Effect m_Effect;
        private Dictionary<String, EffectHandle> m_Parameters = new Dictionary<string, EffectHandle>();
        private VertexDeclaration m_VertexDecl;

        public String ShaderFileName
        {
            get { return m_ShaderFileName; }
        }

        public Effect Effect
        {
            get { return m_Effect; }
        }

        public Dictionary<String, EffectHandle> Parameters
        {
            get { return m_Parameters; }
        }

        public VertexDeclaration VertexDecl
        {
            get { return m_VertexDecl; }
        }

        public Shader(Device device, String shaderFileName)
        {
            m_ShaderFileName = shaderFileName;

            m_Effect = Effect.FromFile(device, shaderFileName, ShaderFlags.Debug | ShaderFlags.SkipOptimization);

            EffectDescription effectDesc = m_Effect.Description;
            for (int i = 0; i < effectDesc.Parameters; i++)
            {
                EffectHandle param = m_Effect.GetParameter(null, i);
                if (param != null)
                {
                    ParameterDescription paramDesc = m_Effect.GetParameterDescription(param);
                    m_Parameters.Add(paramDesc.Name, param);
                }
            }

            List<VertexElement> vertexElements;
            RenderUtils.GetVertexElementsForShader(device, m_Effect, out vertexElements);
            VertexElement[] vtxElems = vertexElements.ToArray();
            m_VertexDecl = new VertexDeclaration(device, vtxElems);
        }

        public void ExecuteTechnique(Device device, EffectHandle technique,
            Matrix matWorld, Matrix matView, Matrix matProjection,
            PassRenderDelegate passRenderer,
            VertexBuffer vertexBuffer, Type vertexType, int vertexCount,
            IndexBuffer indexBuffer, PrimitiveType primitiveType, int primitiveCount)
        {
            Matrix matWorldView = Matrix.Multiply(matWorld, matView);
            Matrix matViewProjection = Matrix.Multiply(matView, matProjection);
            Matrix matWorldViewProjection = Matrix.Multiply(matWorld, matViewProjection);

            if (m_Parameters.ContainsKey("World"))
            {
                EffectHandle param = m_Parameters["World"];
                if (m_Effect.IsParameterUsed(param, technique))
                {
                    m_Effect.SetValue(param, matWorld);
                }
            }
            if (m_Parameters.ContainsKey("WorldInverse"))
            {
                EffectHandle param = m_Parameters["WorldInverse"];
                if (m_Effect.IsParameterUsed(param, technique))
                {
                    Matrix matWorldInverse = Matrix.Invert(matWorld);
                    m_Effect.SetValue(param, matWorldInverse);
                }
            }
            if (m_Parameters.ContainsKey("View"))
            {
                EffectHandle param = m_Parameters["View"];
                if (m_Effect.IsParameterUsed(param, technique))
                {
                    m_Effect.SetValue(param, matView);
                }
            }
            if (m_Parameters.ContainsKey("ViewInverse"))
            {
                EffectHandle param = m_Parameters["ViewInverse"];
                if (m_Effect.IsParameterUsed(param, technique))
                {
                    Matrix matViewInverse = Matrix.Invert(matView);
                    m_Effect.SetValue(param, matViewInverse);
                }
            }
            if (m_Parameters.ContainsKey("WorldView"))
            {
                EffectHandle param = m_Parameters["WorldView"];
                if (m_Effect.IsParameterUsed(param, technique))
                {
                    m_Effect.SetValue(param, matWorldView);
                }
            }
            if (m_Parameters.ContainsKey("WorldViewInverse"))
            {
                EffectHandle param = m_Parameters["WorldViewInverse"];
                if (m_Effect.IsParameterUsed(param, technique))
                {
                    Matrix matWorldViewInverse = Matrix.Invert(matWorldView);
                    m_Effect.SetValue(param, matWorldViewInverse);
                }
            }
            if (m_Parameters.ContainsKey("ViewProjection"))
            {
                EffectHandle param = m_Parameters["ViewProjection"];
                if (m_Effect.IsParameterUsed(param, technique))
                {
                    m_Effect.SetValue(param, matViewProjection);
                }
            }
            if (m_Parameters.ContainsKey("ViewProjectionInverse"))
            {
                EffectHandle param = m_Parameters["ViewProjectionInverse"];
                if (m_Effect.IsParameterUsed(param, technique))
                {
                    Matrix matViewProjectionInverse = Matrix.Invert(matViewProjection);
                    m_Effect.SetValue(param, matViewProjectionInverse);
                }
            }
            if (m_Parameters.ContainsKey("WorldViewProjection"))
            {
                EffectHandle param = m_Parameters["WorldViewProjection"];
                if (m_Effect.IsParameterUsed(param, technique))
                {
                    m_Effect.SetValue(param, matWorldViewProjection);
                }
            }
            if (m_Parameters.ContainsKey("WorldViewProjectionInverse"))
            {
                EffectHandle param = m_Parameters["WorldViewProjectionInverse"];
                if (m_Effect.IsParameterUsed(param, technique))
                {
                    Matrix matWorldViewProjectionInverse = Matrix.Invert(matWorldViewProjection);
                    m_Effect.SetValue(param, matWorldViewProjectionInverse);
                }
            }

            m_Effect.Technique = technique;
            int numPasses = m_Effect.Begin();
            for (int pass = 0; pass < numPasses; pass++)
            {
                m_Effect.BeginPass(pass);
                {
                    passRenderer(device, vertexBuffer, vertexType, vertexCount, indexBuffer, primitiveType, primitiveCount);
                }
                m_Effect.EndPass();
            }
            m_Effect.End();
        }

        public int CompareTo(Object obj)
        {
            Shader other = obj as Shader;
            return m_ShaderFileName.CompareTo(other.ShaderFileName);
        }
    }
}
